import buffer from '@ohos.buffer';
import CryptoJS from '@ohos/crypto-js';
import crypto from '@ohos.security.cryptoFramework';
import util from '@ohos.util';
import { bufferToString } from './utils';

export async function computeShortMac(data: string | buffer.Buffer) {
    if (typeof data === 'string') {
        let array = new Uint8Array(new ArrayBuffer(data.length));
        for (let i = 0, il = data.length; i < il; i++) {
            let value = data.charCodeAt(i);
            array[i] = value > 0xFF ? 0x20 : value;
        }
        data = buffer.from(array.buffer);
    }
    const mac = await computeMac(buffer.from(data))
    return (mac[0] << 7) | (mac[1] >> 1)
}

export async function computeMac(data: buffer.Buffer) {
    let dataIndex: number, odd = true, work = buffer.alloc(16, 0xff);
    for (dataIndex = 0; dataIndex < data.length; dataIndex += 16, odd = !odd)
        work = await aesEcbEncryptThenXor(data, work.buffer, dataIndex);
    if (odd)
        work = await aesEcbEncryptThenXor(buffer.alloc(16, 0), work.buffer);
    const padding = buffer.alloc(16, 0)
    for (let i = data.length * 8,
        paddingIndex = padding.length - 1; i > 0 && paddingIndex >= 0; i >>= 8, paddingIndex--)
        padding[paddingIndex] = i;
    work = await aesEcbEncryptThenXor(padding, work.buffer);
    return await aesEcbEncryptThenXor(work, work.buffer);
}

export async function aesEcbEncryptThenXor(rawKey: buffer.Buffer, data: ArrayBuffer, offset = 0) {
    let key: buffer.Buffer;
    if (offset > 0 || rawKey.length != 16) {
        key = buffer.alloc(16, 0);
        rawKey.copy(key, 0, offset)
    } else
        key = rawKey;
    let buf = await aes128ECBEncrypt(key.buffer, data);
    return xor(data, buf.buffer);
}

export async function aes128ECBEncrypt(key: ArrayBuffer, data: ArrayBuffer) {
    let aesGenerator = crypto.createSymKeyGenerator('AES128');
    let symKey = await aesGenerator.convertKey({ data: new Uint8Array(key) });
    let mode = crypto.CryptoMode.ENCRYPT_MODE;
    let cipher = crypto.createCipher('AES128|ECB|NoPadding');
    await cipher.init(mode, symKey, null);
    let updateOutput = await cipher.doFinal({ data: new Uint8Array(data) });
    return buffer.from(updateOutput.data);
}

export async function aes128ECBDecrypt(key: ArrayBuffer, data: ArrayBuffer) {
    let aesGenerator = crypto.createSymKeyGenerator('AES128');
    let symKey = await aesGenerator.convertKey({ data: new Uint8Array(key) });
    let mode = crypto.CryptoMode.DECRYPT_MODE;
    let cipher = crypto.createCipher('AES128|ECB|NoPadding');
    await cipher.init(mode, symKey, null);
    let updateOutput = await cipher.doFinal({ data: new Uint8Array(data) });
    return buffer.from(updateOutput.data);
}

export async function aes256CBCEncrypt(key: ArrayBuffer, data: ArrayBuffer, iv: ArrayBuffer) {
    let aesGenerator = crypto.createSymKeyGenerator('AES256');
    let symKey = await aesGenerator.convertKey({ data: new Uint8Array(key) });
    let mode = crypto.CryptoMode.ENCRYPT_MODE;
    let cipher = crypto.createCipher('AES256|CBC|NoPadding');
    let ivParamsSpec: crypto.IvParamsSpec = {
        algName: "IvParamsSpec",
        iv: { data: new Uint8Array(iv) }
    };
    await cipher.init(mode, symKey, ivParamsSpec);
    let updateOutput = await cipher.doFinal({ data: new Uint8Array(data) });
    return buffer.from(updateOutput.data);
}

export async function aes256CBCDecrypt(key: ArrayBuffer, data: ArrayBuffer, iv: ArrayBuffer) {
    let aesGenerator = crypto.createSymKeyGenerator('AES256');
    let symKey = await aesGenerator.convertKey({ data: new Uint8Array(key) });
    let mode = crypto.CryptoMode.DECRYPT_MODE;
    let cipher = crypto.createCipher('AES256|CBC|NoPadding');
    let ivParamsSpec: crypto.IvParamsSpec = {
        algName: "IvParamsSpec",
        iv: { data: new Uint8Array(iv) }
    };
    await cipher.init(mode, symKey, ivParamsSpec);
    let updateOutput = await cipher.doFinal({ data: new Uint8Array(data) });
    return buffer.from(updateOutput.data);
}

export function sha256Hash(data: ArrayBuffer) {
    let input = bufferToString(new Uint8Array(data));
    let result: string = CryptoJS.SHA256(input).toString();
    return buffer.from(result);
}

export async function sha256Hmac(key: ArrayBuffer, data: ArrayBuffer) {
    let macKey = bufferToString(new Uint8Array(key)),
        macInput = bufferToString(new Uint8Array(data));
    let result: string = CryptoJS.HmacSHA256(macKey, macInput).toString();
    return buffer.from(result);
}

export async function PBKDF2Sha256(dklen: number, password: ArrayBuffer, salt: ArrayBuffer, iterationCount: number) {
    let spec: crypto.PBKDF2Spec = {
        algName: 'PBKDF2',
        iterations: iterationCount,
        salt: new Uint8Array(salt),
        password: new Uint8Array(password),
        keySize: dklen
    };
    let kdf = crypto.createKdf('PBKDF2|SHA256');
    let secret = await kdf.generateSecret(spec);
    return buffer.from(secret.data);
}

function xor(a: ArrayBuffer, b: ArrayBuffer) {
    const c = buffer.from(a);
    for (let i = 0; i < a.byteLength && i < b.byteLength; i++)
        c[i] ^= b[i];
    return c;
}